﻿using System;
using System.Collections.Generic;
using Munq.LifetimeManagers;
using Munq;

namespace Southern.Models.Southern.Base
{
    /// <summary>
    /// The type of storage to store lifetime of IoC values
    /// </summary>
    /// <author>
    /// </author>
    public enum IoCLifetimeManagerType
    {
        AlwaysNew,
        Cached,
        Request,
        Session,
        ThreadLocalStorage
    }

    /// <summary>
    /// 
    /// </summary>
    public class Services
    {
        private static HashSet<Type> hashset = new HashSet<Type>();
        private static IoCLifetimeManagerType currentLifeTimeManagerType;

        #region Fields

        private static readonly IocContainer _container = new IocContainer();

        #endregion Fields

        static Services()
        {
            _container.UsesDefaultLifetimeManagerOf(GetLifetimeManager());
        }

        #region Methods

        /// <summary>
        /// Wrap registration method to add Registration instance to Hashset
        /// to cahce using later
        /// </summary>
        /// <param name="registration"></param>
        /// <returns></returns>
        private static IRegistration WrapRegistration(IRegistration registration, Type type)
        {
            hashset.Add(type);
            return registration;
        }

        private static ILifetimeManager GetLifetimeManager(IoCLifetimeManagerType lifetimeManager = IoCLifetimeManagerType.AlwaysNew)
        {
            currentLifeTimeManagerType = lifetimeManager;
            switch (lifetimeManager)
            {
                case IoCLifetimeManagerType.Cached:
                    return new CachedLifetime();
                case IoCLifetimeManagerType.Request:
                    return new RequestLifetime();
                case IoCLifetimeManagerType.Session:
                    return new SessionLifetime();
                case IoCLifetimeManagerType.ThreadLocalStorage:
                    return new ThreadLocalStorageLifetime();
                default:
                    return new AlwaysNewLifetime();
            }
        }

        /// <summary>
        /// Set the lifetime manager to current IoC container. All current instance of registrations 
        /// will be removed
        /// </summary>
        /// <param name="lifetimeManager"></param>
        public static void SetLifeTimeManager(IoCLifetimeManagerType lifetimeManager)
        {
            if (lifetimeManager == currentLifeTimeManagerType)
                return;

            // Remove existing instance of all registrations before change to new lifetimeManager
            foreach (Type type in hashset)
            {
                foreach (var reg in _container.GetRegistrations(type))
                    _container.DefaultLifetimeManager.InvalidateInstanceCache(reg);
            }
            
            // Apply new Lifetime Manager to IoC container
            _container.UsesDefaultLifetimeManagerOf(GetLifetimeManager(lifetimeManager));
        }

        public static void Dispose()
        {
            if (null != _container) 
                _container.Dispose();
        }

        public static IRegistration RegisterType<TType>(Func<IDependencyResolver, TType> register)
            where TType : class
        {
            return WrapRegistration(_container.Register<TType>(register), typeof(TType));
        }

		public static IRegistration RegisterType(Type type, Func<IDependencyResolver, object> register)
		{
			return WrapRegistration(_container.Register(type, register), type);
		}

        /// <summary>
        /// Always return the same instances regardless of which lifetime manager is used
        /// </summary>
        /// <typeparam name="TType">Type of object which will be initialized</typeparam>
        /// <param name="register"></param>
        /// <returns></returns>
        public static IRegistration RegisterInstance<TType>(TType instance)
            where TType : class
        {
            return WrapRegistration(_container.RegisterInstance<TType>(instance), typeof(TType));
        }

		/// <summary>
		/// Always return the same instances regardless of which lifetime manager is used
		/// </summary>
		/// <typeparam name="TType">Type of object which will be initialized</typeparam>
		/// <param name="register"></param>
		/// <returns></returns>
		public static IRegistration RegisterInstance<TType>(TType instance, string addKey)
			where TType : class
		{
            return WrapRegistration(_container.RegisterInstance<TType>(addKey, instance), typeof(TType));
		}

        public static IRegistration RegisterTypeForLazyGet<TType>(Func<IDependencyResolver, TType> register)
        {
            Func<TType> lazyResolve = () => register(_container);
            return WrapRegistration(_container.RegisterInstance<Func<TType>>(lazyResolve), typeof(TType));
        }

        /// <summary>
        /// Get the instance of <see cref="TType"/>
        /// </summary>
        /// <typeparam name="TType"></typeparam>
        /// <exception cref="System.Collections.Generic.KeyNotFoundException"></exception>
        /// <returns></returns>
        public static TType Get<TType>()
            where TType: class
        {
            return _container.Resolve<TType>();
        }

        /// <summary>
        /// Get the instance of <see cref="TType"/>
        /// </summary>
        /// <typeparam name="TType"></typeparam>
        /// <exception cref="System.Collections.Generic.KeyNotFoundException"></exception>
        /// <returns></returns>
        public static Func<TType> LazyGet<TType>()
            where TType : class
        {
            return _container.Resolve<Func<TType>>();
        }

        public static IRegistration RegisterType<TType>(string name, Func<IDependencyResolver, TType> register)
            where TType : class
        {
            return WrapRegistration(_container.Register<TType>(name, register), typeof(TType));
        }

        /// <summary>
        /// Get the instance of <see cref="TType"/> with registration name
        /// </summary>
        /// <typeparam name="TType"></typeparam>
        /// <param name="name"></param>
        /// <exception cref="System.Collections.Generic.KeyNotFoundException"></exception>
        /// <returns></returns>
        public static TType Get<TType>(string name)
            where TType : class
        {
            return _container.Resolve<TType>(name);
        }

        /// <summary>
        /// Try to get the instance of <see cref="TType"/>
        /// </summary>
        /// <typeparam name="TType"></typeparam>
        /// <param name="name"></param>
        /// <returns></returns>
        public static bool TryGet<TType>(ref TType obj)
            where TType : class
        {
            if (_container.CanResolve<TType>())
            {
                obj = Get<TType>();
                return true;
            }
            return false;
        }

        /// <summary>
        /// Try to get the instance of <see cref="TType"/>
        /// </summary>
        /// <typeparam name="TType"></typeparam>
        /// <param name="name"></param>
        /// <returns></returns>
        public static bool TryGet<TType>(string name, ref TType obj)
            where TType : class
        {
            if (_container.CanResolve<TType>(name))
            {
                obj = Get<TType>(name);
                return true;
            }
            return false;
        }

        #endregion Methods
    }
}
